package backend

import (
	modelServer "awesomeProject1/models/server"
	"awesomeProject1/pkg/e"
	"awesomeProject1/pkg/gintool"
	"awesomeProject1/pkg/router"
	"awesomeProject1/pkg/validate"
	"awesomeProject1/server"
	"awesomeProject1/services/chain"
	"awesomeProject1/services/contract/invoking"
	"awesomeProject1/services/loan/detail"
	"awesomeProject1/services/organization"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/configor"
	"github.com/simplechain-org/client/log"
	"github.com/simplechain-org/go-simplechain/accounts/keystore"
	"github.com/simplechain-org/go-simplechain/common"
	"io/ioutil"
	"path"
	"strings"
	"sync/atomic"
	"time"
	"xorm.io/xorm"
)

type backend struct {
	name         string
	conf         *modelServer.Config
	db           *xorm.Engine
	dbOnlineList []*xorm.Engine
	engine       *gin.Engine

	loanChainService chain.Service

	key              *keystore.Key
	contractAccount  *keystore.Key
	contractAccounts []*keystore.Key

	detailServ          detail.Service
	organizationService organization.Service
	loanContractService invoking.Service

	isStop uint32
	stop   chan struct {
	}
	operator string
}

func New(name string) server.Server {
	s := new(backend)
	s.name = name
	return s
}

func (b *backend) FnCallCountUpdate(date, fnName string) {
}

func (b *backend) Run(configPath string) error {
	var err error
	// config
	if err = b.config(configPath); err != nil {
		return fmt.Errorf("bs.config(): %s", err.Error())
	}
	server.SetLogConfig(b.conf.Log)

	// mysql
	if err = b.initDB(); err != nil {
		return fmt.Errorf("b.initDB(): %s", err.Error())
	}

	// mysql online
	if err = b.initDBOnline(); err != nil {
		return fmt.Errorf("b.initDB(): %s", err.Error())
	}

	// router
	if err = b.router(); err != nil {
		return fmt.Errorf("bs.router(): %s", err.Error())
	}

	// 初始化账户
	if err = b.chainAccountInit(); err != nil {
		return fmt.Errorf("ps.sipcAccountInit(): %s", err.Error())
	}
	//链
	loanChainService, err := chain.New(b.conf.Chain, b.conf.Account.GasLimit, b.conf.Account.GasPrice, b.key, true)
	if err != nil {
		return fmt.Errorf("chain.New(): %s", err.Error())
	}
	b.loanChainService = loanChainService

	b.detailServ = detail.New(b.db)

	organizationService := organization.New(b.db)
	b.organizationService = organizationService

	contractAddr := common.HexToAddress(b.conf.Contract.ContractAddr)
	loanContractService, code, errStr := invoking.New(contractAddr, b.loanChainService.GetChainClient().GetEthClient(),
		b.conf.Account.GasPrice, b.contractAccount, b.contractAccounts)
	if code != e.Ok {
		log.Error(e.GetMsg(code), "err", errStr)
		//return errors.New(errStr)
	}
	b.loanContractService = loanContractService

	// init
	if err = b.init(); err != nil {
		return fmt.Errorf("b.init(): %s", err.Error())
	}

	// port
	return b.engine.Run(":" + b.conf.Common.ListenPort)
}

func (b *backend) Close() error {
	if atomic.CompareAndSwapUint32(&b.isStop, 0, 1) {
		if b.db != nil {
			if err := b.db.Close(); err != nil {
				return err
			}
		}

		for _, engine := range b.dbOnlineList {
			if engine != nil {
				if err := engine.Close(); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func (b *backend) config(configPath string) error {
	b.conf = new(modelServer.Config)
	err := configor.Load(b.conf, configPath)
	if err != nil {
		return err
	}
	return nil
}

func (b *backend) router() error {
	r := gin.New()
	r.ForwardedByClientIP = true
	r.Use(gin.Logger())
	r.Use(gin.Recovery())
	r.Use(gintool.Logger(b))
	gintool.UseSession(r)

	var prefix = ""

	//添加内部调用接口
	router.AddRouteGroups(r, prefix, RouteGroups(b))

	//自定义参数验证标签
	validate.Init()
	b.engine = r
	return nil
}

func (b *backend) init() error {
	return nil
}

func (b *backend) chainAccountInit() error {
	k, err := chain.GetKey(b.conf.Account.Keystore, b.conf.Account.Password)
	if err != nil {
		log.Error("get key failed", "err", err.Error())
		return err
	}
	b.key = k

	//获取合约主账户
	contractAccount, err := chain.GetKey(b.conf.Account.ContractAccount, b.conf.Account.Password)
	if err != nil {
		log.Error("get ps.conf.Account.ContractAccount failed", "err", err.Error())
		return err
	}
	b.contractAccount = contractAccount

	log.Info("reading contract accounts keystore file,please wait...")
	start := time.Now()
	/*获取合约账户列表*/
	files, err := ioutil.ReadDir(b.conf.Account.ContractAccounts)
	if err != nil {
		log.Error("get contractAccounts file path failed", "err", err.Error())
		return err
	}
	//去除非私钥文件
	index := 0
	for _, v := range files {
		if strings.HasPrefix(v.Name(), "UTC--") {
			files[index] = v
			index++
		}
	}
	contractAccounts := files[:index]

	keys := make([]*keystore.Key, 0, len(contractAccounts))
	for _, file := range contractAccounts {
		k, err := chain.GetKey(path.Join(b.conf.Account.ContractAccounts, file.Name()), b.conf.Account.Password)
		if err != nil {
			log.Error("get key failed", "fileName", file.Name(), "err", err.Error())
			return err
		}
		keys = append(keys, k)
	}
	log.Info("reading contract accounts keystore file finish", "costs", time.Now().Sub(start))
	if len(keys) < 5 {
		err = errors.New("合约账户数量少于5个，请添加。提示：config/contractAccounts目录")
		return err
	}
	b.contractAccounts = keys

	return nil
}
